
#include <gtest\gtest.h>
#include <vector>
#include <iostream>
#include <boost/smart_ptr.hpp>
#include <allegro5/allegro.h>
#include <allegro5/allegro_primitives.h>
#include <allegro5/allegro_native_dialog.h>


#include "Bitmap.h"
#include "colour.h"
#include "Digdug.h"
#include "Display.h"
#include "Game.h"
#include "GlobalVariables.h"
#include "LevelLoader.h"
#include "Map.h"
#include "Monster.h"
#include "Movables.h"
#include "Point.h"
#include "Projectile.h"
#include "Utilities.h"
#include "Keyboard.h"

using boost::shared_ptr;
using boost::scoped_ptr;
using std::vector;

//bool init = al_init();
//bool prim = al_init_primitives_addon();

//Create a display object which initializes Allegro.
//Used for some of the graphical unit tests.
 scoped_ptr<Display> display_ptr(new Display);

//bool init = al_init();	
//bool prim_init = al_init_primitives_addon();

/////////////////////////////////////
//		Tests for Point Class
/////////////////////////////////////

TEST(Point, identicalPointsAreEqual)
{
	Point point1(25, 25);
	Point point2(25, 25);

	EXPECT_TRUE(point1 == point2);
}

TEST(Point, nonIdenticalPointsAreNotEqual)
{
	Point point1(25, 25);
	Point point2(30, 25);

	EXPECT_FALSE(point1 == point2);
}

/////////////////////////////////////
//		Tests for Utilities
/////////////////////////////////////


//Tests for delta function in utilities header file
TEST(Utilities, deltaFunctionReturnsCorrectValuesForUPDirection)
{
	vector<int> delta_values = delta(UP);

	EXPECT_EQ(delta_values.at(0), 0);
	EXPECT_EQ(delta_values.at(1), -1);
}

TEST(Utilities, deltaFunctionReturnsCorrectValuesForDOWNDirection)
{
	vector<int> delta_values = delta(DOWN);

	EXPECT_EQ(delta_values.at(0), 0);
	EXPECT_EQ(delta_values.at(1), 1);
}

TEST(Utilities, deltaFunctionReturnsCorrectValuesForLEFTDirection)
{
	vector<int> delta_values = delta(LEFT);

	EXPECT_EQ(delta_values.at(0), -1);
	EXPECT_EQ(delta_values.at(1), 0);
}

TEST(Utilities, deltaFunctionReturnsCorrectValuesForRIGHTDirection)
{
	vector<int> delta_values = delta(RIGHT);

	EXPECT_EQ(delta_values.at(0), 1);
	EXPECT_EQ(delta_values.at(1), 0);
}


//Tests for contains function in utilities header file
TEST(Utilities, doesNotContainTestedDirectionIfEmptyDirectionVectorIsTested)
{
	vector<Direction> directions;
	directions.clear();

	EXPECT_FALSE(contains(directions, UP));
	EXPECT_FALSE(contains(directions, DOWN));
	EXPECT_FALSE(contains(directions, LEFT));
	EXPECT_FALSE(contains(directions, RIGHT));
}


TEST(Utilities, containsDirectionIfTestedDirectionIsFoundOnceInTestedVector)
{
	vector<Direction> directions;
	directions.push_back(LEFT);
	directions.push_back(RIGHT);
	directions.push_back(DOWN);
	directions.push_back(LEFT);

	EXPECT_TRUE(contains(directions, RIGHT));
}


TEST(Utilities, containsDirectionIfTestedDirectionIsFoundMultipleTimesInTestedVector)
{
	vector<Direction> directions;
	directions.push_back(LEFT);
	directions.push_back(RIGHT);
	directions.push_back(DOWN);
	directions.push_back(LEFT);

	EXPECT_TRUE(contains(directions, LEFT));
}

TEST(Utilities, doesNotcontainDirectionIfTestedDirectionIsNotContainedInTestedVector)
{
	vector<Direction> directions;
	directions.push_back(LEFT);
	directions.push_back(RIGHT);
	directions.push_back(DOWN);
	directions.push_back(LEFT);

	EXPECT_FALSE(contains(directions, UP));
}

/////////////////////////////////////
//		Tests for LevelLoader
/////////////////////////////////////

//LevelLoader throws a string when an invalid level is passed to the 
//constructor class. 
TEST(LevelLoader, throwsExceptionWhenLoadingFileThatDoesNotExist)
{
	EXPECT_THROW({LevelLoader invalidLevel(123);}, string );
}

//LevelLoaderClass creates an object without any throws when
//A file that corresponds to the given level exists
TEST(LevelLoader, aValidFileIsSucessfullyLoadedByConstructor)
{
	EXPECT_NO_THROW({LevelLoader validLevel(100);});
}

//The known position of digdug is correctly loaded from a file
TEST(LevelLoader, digdugsPositionIsCorrectlyLoadedFromFile)
{
	LevelLoader game_data(100);
	Point digdug_location(24,9);
	EXPECT_TRUE(game_data.getDigdugPosition() == digdug_location);
}

//All Monsters are correcly loaded from a file
TEST(LevelLoader, monstersPositionIsCorrectlyLoadedFromFile)
{	
	LevelLoader game_data(100);		
	vector <Point> loaded_monster_data = game_data.getMonstersPosition();
	vector <Point> expected_monster_data;
	
	expected_monster_data.push_back(Point(9,14));
	expected_monster_data.push_back(Point(39,14));

	EXPECT_TRUE(loaded_monster_data == expected_monster_data);
}

//Map Data is correctly loaded
TEST(LevelLoader, mapDataIsCorrectlyLoadedFromAFile)
{	
	LevelLoader game_data(102);		

	vector<vector<Movable_type>> expected_map;
	for(int x = 0; x < 50; x++)
	{
		expected_map.push_back(vector<Movable_type>());
		for(int y = 0; y < 50; y++)
		{
			if( x !=24 || y !=24)
				expected_map.back().push_back(SAND);
			else
				expected_map.back().push_back(HOLE);
		}
	}

	EXPECT_TRUE(expected_map == game_data.getMapData());
}


//For testing purposes a map starting at level_100, stored as a txt, is used to contain the data for the test map.
//The map level_100 contains sand except for a line of holes starting (25, 0) till (25, 25)


/////////////////////////////////////
//		Tests for Map Class
/////////////////////////////////////


TEST(Map, canIdentifyElementTypeAtGivenPositionOnMap)
{
	//First load the map with the level data. 
	
	LevelLoader test_level(100);
	Map test_map;
	test_map.set(test_level.getMapData()); // This loads a map containing sand and holes at positions listed above

	EXPECT_TRUE(test_map.positionType(Point(25, 5)) == HOLE);
	EXPECT_TRUE(test_map.positionType(Point(5, 5)) == SAND);

	EXPECT_FALSE(test_map.positionType(Point(5, 5)) == HOLE);
	EXPECT_FALSE(test_map.positionType(Point(25, 5)) == SAND);

}

//Test plotting of movables to map

TEST(Map, canPlotDigdugWithGivenRadiusToPositionOnMap)
{
//First load the map with the level data. 
	
	LevelLoader test_level(100);
	Map test_map;
	test_map.set(test_level.getMapData()); // This loads a map containing sand and holes, with no movables

	//check that digdug is not at test position
	EXPECT_FALSE( test_map.positionType(Point(24, 24)) == DIGDUG );
	EXPECT_FALSE( test_map.positionType(Point(23, 23)) == DIGDUG );
	EXPECT_FALSE( test_map.positionType(Point(23, 25)) == DIGDUG );


	test_map.plotMovable(DIGDUG, Point(24, 24), 1);

	EXPECT_TRUE( test_map.positionType(Point(24, 24)) == DIGDUG );
	EXPECT_TRUE( test_map.positionType(Point(23, 23)) == DIGDUG );
	EXPECT_TRUE( test_map.positionType(Point(23, 25)) == DIGDUG );
	EXPECT_FALSE( test_map.positionType(Point(26, 25)) == DIGDUG );
}

TEST(Map, canPlotMonsterWithGivenRadiusToPositionOnMap)
{
//First load the map with the level data. 
	
	LevelLoader test_level(100);
	Map test_map;
	test_map.set(test_level.getMapData()); // This loads a map containing sand and holes, with no movables

	//check that monster is not at test position
	EXPECT_FALSE( test_map.positionType(Point(24, 24)) == MONSTER );
	EXPECT_FALSE( test_map.positionType(Point(23, 23)) == MONSTER );
	EXPECT_FALSE( test_map.positionType(Point(23, 25)) == MONSTER );


	test_map.plotMovable(MONSTER, Point(24, 24), 1);

	EXPECT_TRUE( test_map.positionType(Point(24, 24)) == MONSTER );
	EXPECT_TRUE( test_map.positionType(Point(23, 23)) == MONSTER );
	EXPECT_TRUE( test_map.positionType(Point(23, 25)) == MONSTER );
	EXPECT_FALSE( test_map.positionType(Point(26, 25)) == MONSTER );
}

TEST(Map, canPlotProjectileWithGivenRadiusToPositionOnMap)
{
//First load the map with the level data. 
	
	LevelLoader test_level(100);
	Map test_map;
	test_map.set(test_level.getMapData()); // This loads a map containing sand and holes, with no movables

	//check that projectile is not at test position
	EXPECT_FALSE( test_map.positionType(Point(24, 24)) ==	PROJECTILE );
	EXPECT_FALSE( test_map.positionType(Point(23, 23)) == PROJECTILE );
	EXPECT_FALSE( test_map.positionType(Point(23, 25)) == PROJECTILE );


	test_map.plotMovable(PROJECTILE, Point(24, 24), 1);

	EXPECT_TRUE( test_map.positionType(Point(24, 24)) == PROJECTILE );
	EXPECT_TRUE( test_map.positionType(Point(23, 23)) == PROJECTILE );
	EXPECT_TRUE( test_map.positionType(Point(23, 25)) == PROJECTILE );
	EXPECT_FALSE( test_map.positionType(Point(26, 25)) == PROJECTILE );
}

//Test removal of movables from map

TEST(Map, canRemoveDigdugWithGivenRadiusFromPositionOnMap)
{
//First load the map with the level data. 
	
	LevelLoader test_level(100);
	Map test_map;
	test_map.set(test_level.getMapData()); // This loads a map containing sand and holes, with no movables

	//Plot Digdug on map
	test_map.plotMovable(DIGDUG, Point(24, 24), 1);


	//check that digdug is at test position
	EXPECT_TRUE( test_map.positionType(Point(24, 24)) == DIGDUG );
	EXPECT_TRUE( test_map.positionType(Point(23, 23)) == DIGDUG );
	EXPECT_TRUE( test_map.positionType(Point(23, 25)) == DIGDUG );

	//Remove Digdug From Map
	test_map.removeMovable(Point(24, 24), 1);

	EXPECT_FALSE( test_map.positionType(Point(24, 24)) == DIGDUG );
	EXPECT_FALSE( test_map.positionType(Point(23, 23)) == DIGDUG );
	EXPECT_FALSE( test_map.positionType(Point(23, 25)) == DIGDUG );
}

TEST(Map, canRemoveMonsterWithGivenRadiusFromPositionOnMap)
{
//First load the map with the level data. 
	
	LevelLoader test_level(100);
	Map test_map;
	test_map.set(test_level.getMapData()); // This loads a map containing sand and holes, with no movables

	//Plot Digdug on map
	test_map.plotMovable(MONSTER, Point(24, 24), 1);


	//check that monster is at test position
	EXPECT_TRUE( test_map.positionType(Point(24, 24)) == MONSTER );
	EXPECT_TRUE( test_map.positionType(Point(23, 23)) == MONSTER );
	EXPECT_TRUE( test_map.positionType(Point(23, 25)) == MONSTER );

	//Remove Digdug From Map
	test_map.removeMovable(Point(24, 24), 1);

	EXPECT_FALSE( test_map.positionType(Point(24, 24)) == MONSTER );
	EXPECT_FALSE( test_map.positionType(Point(23, 23)) == MONSTER );
	EXPECT_FALSE( test_map.positionType(Point(23, 25)) == MONSTER );
}

TEST(Map, canRemoveProjectileWithGivenRadiusFromPositionOnMap)
{
//First load the map with the level data. 
	
	LevelLoader test_level(100);
	Map test_map;
	test_map.set(test_level.getMapData()); // This loads a map containing sand and holes, with no movables

	//Plot Digdug on map
	test_map.plotMovable(PROJECTILE, Point(24, 24), 1);


	//check that projectile is at test position
	EXPECT_TRUE( test_map.positionType(Point(24, 24)) == PROJECTILE );
	EXPECT_TRUE( test_map.positionType(Point(23, 23)) == PROJECTILE );
	EXPECT_TRUE( test_map.positionType(Point(23, 25)) == PROJECTILE );

	//Remove Digdug From Map
	test_map.removeMovable(Point(24, 24), 1);

	EXPECT_FALSE( test_map.positionType(Point(24, 24)) == PROJECTILE );
	EXPECT_FALSE( test_map.positionType(Point(23, 23)) == PROJECTILE );
	EXPECT_FALSE( test_map.positionType(Point(23, 25)) == PROJECTILE );
}

/////////////////////////////////////
//		Tests for Digdug Class
/////////////////////////////////////

TEST(Digdug, canMoveDigDugInSpecifiedDirectionThroughHole)
{
	//Create digdug at given position
	Digdug digdug(Point(23, 25));
	
	//Load map with level data
	LevelLoader test_level(100);
	Map map;
	map.set(test_level.getMapData()); // This loads a map containing sand and holes, with no movables

	//Plot Digdug on map
	map.plotMovable(DIGDUG, digdug.location(), 1);

	//Tell digdug to move LEFT
	digdug.allowMove(LEFT);
	
	EXPECT_TRUE(digdug.move(map) == CAN_MOVE);

}

TEST(Digdug, canMoveDigDugInSpecifiedDirectionThroughSand)
{
	//Create digdug at given position
	Digdug digdug(Point(23, 25));
	
	//Load map with level data
	LevelLoader test_level(100);
	Map map;
	map.set(test_level.getMapData()); // This loads a map containing sand and holes, with no movables

	//Plot Digdug on map
	map.plotMovable(DIGDUG, digdug.location(), 1);

	//Tell digdug to move LEFT
	digdug.allowMove(LEFT);
	
	EXPECT_TRUE(digdug.move(map) == CAN_MOVE);

}

TEST(Digdug, digdugDiesWhenMovingIntoMonster)
{
	//Create Digdug at given position
	Digdug digdug(Point(24, 10));
	//create monster at one movable location above digdug
	Monster monster(Point(24, digdug.location().y() - 2*RADIUS_OF_MOVABLE - 1)); 
	
	//Load map with level data
	LevelLoader test_level(100);
	Map map;
	map.set(test_level.getMapData()); 


	map.plotMovable(PROJECTILE, digdug.location(), RADIUS_OF_MOVABLE);
	map.plotMovable(MONSTER, monster.location(), RADIUS_OF_MOVABLE);

	digdug.allowMove(UP);
	
	EXPECT_TRUE(digdug.move(map) == DIGDUG_DEAD);
}

/////////////////////////////////////
//		Tests for Prjoectile Class
/////////////////////////////////////

TEST(Projectile, canMovePrjectileInSpecifiedDirectionThroughHole)
{
	//Create Projectile at given position
	Projectile projectile(Point(24, 10));
	
	//Load map with level data
	LevelLoader test_level(100);
	Map map;
	map.set(test_level.getMapData()); 

	//Plot Digdug on map
	map.plotMovable(PROJECTILE, projectile.location(), 1);

	//Tell projectile to move UP to position containing hole
	projectile.allowMove(UP);
	
	EXPECT_TRUE(projectile.move(map) == CAN_MOVE);
}

TEST(Projectile, projectileHasCollisionWhenMovingThroughSand)
{
	//Create Projectile at given position in sand
	Projectile projectile(Point(30, 30));
	
	//Load map with level data
	LevelLoader test_level(100);
	Map map;
	map.set(test_level.getMapData());


	map.plotMovable(PROJECTILE, projectile.location(), 1);

	projectile.allowMove(RIGHT);
	
	EXPECT_TRUE(projectile.move(map) == COLLISION);
}

TEST(Projectile, projectileKillsMonsterWhenMovingIntoMonster)
{
	//Create Projectile at given position
	Projectile projectile(Point(24, 10));
	//create monster at one movable location above the projectile
	Monster monster(Point(24, projectile.location().y() - 2*RADIUS_OF_MOVABLE - 1)); 
	
	//Load map with level data
	LevelLoader test_level(100);
	Map map;
	map.set(test_level.getMapData()); 


	map.plotMovable(PROJECTILE, projectile.location(), RADIUS_OF_MOVABLE);
	map.plotMovable(MONSTER, monster.location(), RADIUS_OF_MOVABLE);

	projectile.allowMove(UP);
		
	EXPECT_TRUE(projectile.move(map) == MONSTER_DEAD);
}

/////////////////////////////////////
//		Tests for Movables Class
/////////////////////////////////////

TEST(Movables, movableHasCollisionWhenTryingToMoveOutOfTopAndLeftGameBoundaries)
{
	//Load map with level containing only holes
	LevelLoader test_level(101);
	Map map;
	map.set(test_level.getMapData()); // This loads a map containing sand and holes, with no movables

	//Create a movable at top left corner
	scoped_ptr<Movables> movable_ptr(new Digdug(Point(RADIUS_OF_MOVABLE, RADIUS_OF_MOVABLE)));

	EXPECT_TRUE(movable_ptr->checkMoveInDirection(movable_ptr->location(), UP, map) == COLLISION);
	EXPECT_TRUE(movable_ptr->checkMoveInDirection(movable_ptr->location(), LEFT, map) == COLLISION);
}

TEST(Movables, movableHasCollisionWhenTryingToMoveOutOfBottomAndRightGameBoundaries)
{
	//Load map with level containing only holes
	LevelLoader test_level(101);
	Map map;
	map.set(test_level.getMapData()); // This loads a map containing sand and holes, with no movables

	//Create a movable at bottom right corner
	scoped_ptr<Movables> movable_ptr(new Monster(Point(GAME_GRID_ELEMENTS - RADIUS_OF_MOVABLE - 1, GAME_GRID_ELEMENTS - RADIUS_OF_MOVABLE - 1)));

	EXPECT_TRUE(movable_ptr->checkMoveInDirection(movable_ptr->location(), DOWN, map) == COLLISION);
	EXPECT_TRUE(movable_ptr->checkMoveInDirection(movable_ptr->location(), RIGHT, map) == COLLISION);
}

//////////////////////////////////////////////
//		Graphical Tests Requiring User Input
//////////////////////////////////////////////

//////////////////////////////////////////////
//		Tests For Display Class
//////////////////////////////////////////////

TEST(Display, drawsScoreBoxCorrectlyandDisplaysCorrectScores)
{
	display_ptr->clearBuffer();
	display_ptr->drawScoreBox(100, 5, 1000, 10);
	display_ptr->update();

	int answer = display_ptr->showNativeDialogMessageQuestion( "Drawing of Score Box",
	"The score box should be visible on the right hand side of the screen. \nThe following values are expected: \nScore: 100, \nLives: 5, \nHigh Score: 1000, \nLevel: 10. \nIf this is the case, please click 'Yes'. Else click 'No'");
		
	EXPECT_EQ(answer, 1);
}

TEST(Display, drawsMapCorrectly)
{
	//Load map with level containing only holes
	LevelLoader test_level(102);
	Map map;
	map.set(test_level.getMapData()); // This loads a map containing sand and holes, with no movables
	
	display_ptr->clearBuffer();
	display_ptr->drawMap(map);
	display_ptr->update();

	int answer = display_ptr->showNativeDialogMessageQuestion( "Drawing of Map",
	"A map consisting of sand with one small square hole in the middle should be visible. \nIf this is the case, please click 'Yes'. Else click 'No'");
		
	EXPECT_EQ(answer, 1);
}

TEST(Display, drawsMonstersCorrectly)
{
	Monster monster(Point(25, 25));
	vector<Monster> monster_vec;
	monster_vec.push_back(monster);

	display_ptr->clearBuffer();
	display_ptr->drawMonsters(monster_vec);
	display_ptr->update();

	int answer = display_ptr->showNativeDialogMessageQuestion( "Drawing of Monsters","Do you see a monster in the middle of the playing grid?");
		
	EXPECT_EQ(answer, 1);
}

TEST(Display, drawsDigdugCorrectly)
{
	Digdug digdug(Point(25, 25));
	
	display_ptr->clearBuffer();
	display_ptr->drawDigdug(digdug);
	display_ptr->update();

	int answer = display_ptr->showNativeDialogMessageQuestion( "Drawing of Dig Dug","Do you see Dig Dug in the middle of the playing grid?");
		
	EXPECT_EQ(answer, 1);
}

TEST(Display, drawsProjectileCorrectly)
{
	Digdug digdug(Point(20, 25));
	Projectile projectile(Point(25, 25));
	//The projectile must be set to shooting mode in order to be drawn
	projectile.allowMove(RIGHT);

	display_ptr->clearBuffer();
	//The projectile is drawn extending from Digdug's position
	display_ptr->drawProjectile(projectile, digdug);
	display_ptr->update();

	int answer = display_ptr->showNativeDialogMessageQuestion( "Drawing of Projectile","Do you see a projectile in the extending to the right in middle of the playing grid?");
		
	EXPECT_EQ(answer, 1);
}

//////////////////////////////////////////////
//		Tests For Game Class
//////////////////////////////////////////////

//TEST(Display, userCanMoveDigdugInSpecifiedDirection)
//{
//	Game game(100, 1);
//
//	display_ptr->clearBuffer();
//	//The projectile is drawn extending from Digdug's position
//	display_ptr->drawProjectile(projectile, digdug);
//	display_ptr->update();
//
//	int answer = display_ptr->showNativeDialogMessage( "Drawing of Projectile","Do you see a projectile in the extending to the right in middle of the playing grid?");
//		
//	EXPECT_EQ(answer, 1);
//}